[PATCH 13/24] lib-imap: Add imap_parser_params.list_count_limit
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Fri, 6 Mar 2026 13:32:29 +0000 (15:32 +0200)
committerNoah Meyerhans <noahm@debian.org>
Tue, 31 Mar 2026 19:07:17 +0000 (15:07 -0400)
Gbp-Pq: Name CVE-2026-27857-3.patch

src/lib-imap/imap-parser.c
src/lib-imap/imap-parser.h
src/lib-imap/test-imap-parser.c

index 532cb97dfb6e3a2587d7df8d5bb176e1d6994049..6212aed33d85b217f2e183bc446c79c1824da739 100644 (file)
@@ -39,6 +39,7 @@ struct imap_parser {
        struct istream *input;
        struct ostream *output;
        size_t max_line_size;
+       unsigned int list_count_limit;
         enum imap_parser_flags flags;
 
        /* reset by imap_parser_reset(): */
@@ -46,6 +47,7 @@ struct imap_parser {
        ARRAY_TYPE(imap_arg_list) root_list;
         ARRAY_TYPE(imap_arg_list) *cur_list;
        struct imap_arg *list_arg;
+       unsigned int list_count;
 
        enum arg_parse_type cur_type;
        size_t cur_pos; /* parser position in input buffer */
@@ -70,7 +72,7 @@ struct imap_parser {
 struct imap_parser *
 imap_parser_create(struct istream *input, struct ostream *output,
                   size_t max_line_size,
-                  const struct imap_parser_params *params ATTR_UNUSED)
+                  const struct imap_parser_params *params)
 {
        struct imap_parser *parser;
 
@@ -81,6 +83,10 @@ imap_parser_create(struct istream *input, struct ostream *output,
        parser->input = input;
        parser->output = output;
        parser->max_line_size = max_line_size;
+       if (params != NULL && params->list_count_limit > 0)
+               parser->list_count_limit = params->list_count_limit;
+       else
+               parser->list_count_limit = UINT_MAX;
 
        p_array_init(&parser->root_list, parser->pool, LIST_INIT_COUNT);
        parser->cur_list = &parser->root_list;
@@ -122,6 +128,7 @@ void imap_parser_reset(struct imap_parser *parser)
        p_array_init(&parser->root_list, parser->pool, LIST_INIT_COUNT);
        parser->cur_list = &parser->root_list;
        parser->list_arg = NULL;
+       parser->list_count = 0;
 
        parser->cur_type = ARG_PARSE_NONE;
        parser->cur_pos = 0;
@@ -210,6 +217,12 @@ static bool imap_parser_close_list(struct imap_parser *parser)
                parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
                return FALSE;
        }
+       if (parser->list_count >= parser->list_count_limit) {
+               parser->error_msg = "Too many '('";
+               parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX;
+               return FALSE;
+       }
+       parser->list_count++;
 
        arg = imap_arg_create(parser);
        arg->type = IMAP_ARG_EOL;
index 16ef2c7a34e91f777a3705fd9c41b2226b20a8c6..3832cc9b92730d27a63ad0d928420254779c933f 100644 (file)
@@ -39,6 +39,12 @@ enum imap_parser_error {
 };
 
 struct imap_parser_params {
+       /* How many open lists ('(' chars) to allow before faililng the parsing.
+          0 means unlimited. This is mainly used to prevent excessive memory
+          usage in imap-login process. In imap process there are many other
+          ways to increase memory usage, so we let the max_line_size be the
+          only limit. */
+       unsigned int list_count_limit;
 };
 
 struct imap_parser;
index cff2b386c84f5dec834a68619cc4f7f5015fae71..a1347ac0ac4b5bad596b12245ca3e099f75df569 100644 (file)
@@ -2,6 +2,7 @@
 
 #include "lib.h"
 #include "istream.h"
+#include "istream-chain.h"
 #include "imap-parser.h"
 #include "test-common.h"
 
@@ -79,6 +80,50 @@ static void test_imap_parser_partial_list(void)
        test_end();
 }
 
+static void test_imap_parser_list_limit(void)
+{
+       struct {
+               const char *input;
+               int ret;
+       } tests[] = {
+               { "(())\r\n", 1 },
+               { "((()))\r\n", -1 },
+       };
+       struct istream_chain *chain;
+       struct istream *chain_input;
+       struct imap_parser *parser;
+       const struct imap_arg *args;
+
+       test_begin("imap parser list limit");
+       struct imap_parser_params params = {
+               .list_count_limit = 2,
+       };
+
+       for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
+               chain_input = i_stream_create_chain(&chain, SIZE_MAX);
+               parser = imap_parser_create(chain_input, NULL, 1024, &params);
+
+               for (unsigned int j = 0; j < 2; j++) {
+                       struct istream *input =
+                               test_istream_create(tests[i].input);
+                       i_stream_chain_append(chain, input);
+                       i_stream_unref(&input);
+
+                       (void)i_stream_read(chain_input);
+
+                       test_assert_cmp(imap_parser_read_args(parser, 0, 0, &args), ==, tests[i].ret);
+                       /* skip over CRLF */
+                       i_stream_skip(chain_input, i_stream_get_data_size(chain_input));
+
+                       /* make sure parser reset works */
+                       imap_parser_reset(parser);
+               }
+               imap_parser_unref(&parser);
+               i_stream_destroy(&chain_input);
+       }
+       test_end();
+}
+
 static void test_imap_parser_read_tag_cmd(void)
 {
        enum read_type {
@@ -155,6 +200,7 @@ int main(void)
        static void (*const test_functions[])(void) = {
                test_imap_parser_crlf,
                test_imap_parser_partial_list,
+               test_imap_parser_list_limit,
                test_imap_parser_read_tag_cmd,
                NULL
        };